import 'package:piecemeal/piecemeal.dart';

import '../action/action.dart';
import '../core/combat.dart';
import '../core/element.dart';
import '../core/log.dart';
import '../hero/lore.dart';
import '../hero/skill.dart';
import 'affix.dart';
import 'item.dart';

typedef TossItemUse = Action Function(Vec pos);
typedef AddItem = void Function(Item item);

abstract class Drop {
  /// Generates items at [depth] by calling [addItem] for each new item.
  ///
  /// If [lore] is given, then it's used to avoid creating duplicate artifacts.
  /// If [lore] is `null`, then no artifacts will be created.
  void dropItem(Lore? lore, int depth, AddItem addItem);
}

class ItemUse {
  final String description;
  final Action Function() createAction;

  ItemUse(this.description, this.createAction);
}

/// Tracks information about a tossable [ItemType].
class Toss {
  /// The percent chance of the item breaking when thrown. `null` if the item
  /// can't be thrown.
  final int breakage;

  /// The item's attack when thrown or `null` if the item can't be thrown.
  final Attack attack;

  /// The action created when the item is tossed and hits something, or `null`
  /// if it just falls to the ground.
  final TossItemUse? use;

  Toss(this.breakage, this.attack, this.use);
}

/// A kind of [Item]. Each item will have a type that describes the item.
class ItemType {
  /// The pattern string used to generate quantified names for items of this
  /// type: `Scroll[s] of Disappearing`, etc.
  final String quantifiableName;

  /// The singular name of the item type: "Sword", "Scroll of Disappearing".
  ///
  /// This is used to identify the item type in drops, affixes, etc.
  String get name => Log.singular(quantifiableName);

  final Object appearance;

  /// The item types's depth.
  ///
  /// Higher depth objects are found later in the game.
  final int depth;

  final int sortIndex;

  // TODO: These two fields are sort of redundant with tags, but ItemTypes
  // don't own their tags. Should they?

  /// The name of the equipment slot that [Item]s can be placed in. If `null`
  /// then this Item cannot be equipped.
  final String? equipSlot;

  /// If true, the item takes up both hand slots.
  final bool isTwoHanded;

  /// If this item is a weapon, returns which kind of weapon it is -- "spear",
  /// "sword", etc. Otherwise returns `null`.
  final String? weaponType;

  final ItemUse? use;

  /// The item's [Attack] or `null` if the item is not an equippable weapon.
  final Attack? attack;

  /// The items toss information, or `null` if it can't be tossed.
  final Toss? toss;

  final Defense? defense;

  final int armor;

  /// How much gold this item is worth.
  final int price;

  /// The penalty to the hero's strength when wearing this.
  final int weight;

  /// The amount of strength required to wield the item effectively.
  final int heft;

  /// The amount of light this items gives off when equipped.
  ///
  /// This isn't a raw emanation value, but a level to be passed to
  /// [Lighting.emanationFromLevel()].
  final int emanationLevel;

  /// True if this item is "treasure".
  ///
  /// That means it just has a gold value. As soon as the hero steps on it, it
  /// increases the hero's gold and disappears.
  bool isTreasure;

  /// The maximum number of items of this type that a single stack may contain.
  final int maxStack;

  /// Percent chance of this item being destroyed when hit with a given element.
  final Map<Element, int> destroyChance = {};

  /// If the item burns when on the ground, how much fuel it adds to the
  /// burning tile.
  final int fuel;

  /// The [Skill]s discovered when picking up an item of this type.
  final List<Skill> skills = [];

  /// If items of this type have a built-in affix, the affix.
  ///
  /// Some items, like rings and amulets always have some magical effect that
  /// modifies the hero. Instead of reimplementing all of the mechanism in
  /// Affix that handles modifying hero properties, we model those item types
  /// by saying that they have an "intrinsic affix" that provides that effect.
  ///
  /// If an ItemType contains an intrinsicAffix, then every Item of that
  /// ItemType will have that affix. (And no other affixes. There are no "ego"
  /// items that have both an intrinsic affix and other randomly added ones.)
  final AffixType? intrinsicAffix;

  /// Whether this item type is for a unique artifact.
  ///
  /// If `true`, then the hero will only ever see one item of this type.
  final bool isArtifact;

  ItemType(
    this.quantifiableName,
    this.appearance,
    this.depth,
    this.sortIndex,
    this.equipSlot,
    this.weaponType,
    this.use,
    this.attack,
    this.toss,
    this.defense,
    this.armor,
    this.price,
    this.maxStack,
    this.intrinsicAffix, {
    this.weight = 0,
    this.heft = 1,
    int? emanation,
    int? fuel,
    bool? treasure,
    required this.isArtifact,
    bool? twoHanded,
  }) : emanationLevel = emanation ?? 0,
       fuel = fuel ?? 0,
       isTreasure = treasure ?? false,
       isTwoHanded = twoHanded ?? false;

  @override
  String toString() => name;
}
